package com.pj.project.update_package;

import cn.hutool.core.codec.BCD;
import com.pj.project.file_info.FileInfo;
import com.pj.project.file_info.FileInfoService;
import com.pj.project.mqtt.myserver.MainService;
import com.pj.project.mqtt.util.Tools;
import com.pj.project.update_log.UpdateLog;
import com.pj.project.update_log.UpdateLogService;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import static com.pj.project.mqtt.util.Tools.bytesToHex;

/**
 * Service: update_log -- 升级记录
 * @author xxxxxx
 */
@Slf4j
@Controller
public class UpdatePackageService {

	@Autowired
	private FileInfoService fileInfoService;
	@Autowired
	private UpdateLogService updateLogService;

	@Autowired
	private MainService mqttService;

	public static Map<String,PackageInfo> pageMap=new HashMap<>();

	public static Map<String,byte[]> fileByteMap=new HashMap<>();


	// 空
	private final byte NUL = 0x00;
	// 开始
	private final byte SOH = 0x01;
	//正文开始
	private final byte STX = 0x02;
	//正文开始
	private final byte ETX = 0x03;
	// 结束
	private final byte EOT = 0x04;
	// 应答
	private final byte ACK = 0x06;
	// 重传
	private final byte NAK = 0x15;
	// 无条件结束
	private final byte CAN = 0x18;

	//自定义升级完成
	private final byte SUC = 0x17;
	private final byte EM = 0x19;
	private final byte aaa = 0x1A;

	// 以128字节块的形式传输数据
	private final int SECTOR_SIZE = 128;
	// 最大错误（无应答）包数
	private final int MAX_ERRORS = 10;

	/**
	 * 控制
	 * 解析消息，分类处理
	 */
	public void updatePackageCtl(String topic, byte[] bArray){

//		BCD.strToBcd("13");
		log.info("收到：" + topic + "--->" + bytesToHex(bArray));
		switch (bArray[0]){
			case 'N':
				findFileByName(topic,bArray);
				break;
			case 'C':
				sendUpdatePackageFile(topic,bArray);
				break;
			case ACK:
				sendUpdatePackageData(topic,bArray);
				break;
			case NAK:
				clearPageMap(topic);
				break;
			case SUC:
				upDateSuccess(topic);
				break;
			case EM:
				upDateTOErro(topic);
				break;
			default:
				log.error("未找到指令:"+ topic+ "------》" + bytesToHex(bArray));
				return;
		}
	}

	/**
	 * 查询
	 * 通过接收包的头信息，查询对应升级文件，并获取
	 */
	public void findFileByName(String topic,byte[] bArray){
		try {
			String s = new String(bArray, "utf-8");
			String packageSize = s.substring(s.lastIndexOf(","));
			String n_name_size = s.replace(packageSize, "");
			String size1 = packageSize.replace(",", "");
			String n_name = n_name_size.replace("N ", "");
			FileInfo fileByName = fileInfoService.findFileByName(n_name);
			String filePath = fileByName.getRootFolder() + "/" + fileByName.getName();
			DataInputStream inputStream = new DataInputStream(new FileInputStream(filePath));
			byte[] bytes = toByteArray(inputStream);

			PackageInfo packageInfo = new PackageInfo();
			if(!size1.equals(""))
			packageInfo.setPackageSize(Integer.parseInt(size1));

			packageInfo.setTopic(topic);
			packageInfo.setFileName(n_name);
			packageInfo.setBlockNumber(NUL);
//			packageInfo.setFileBytes(bytes);
			fileByteMap.put(n_name,bytes);
			packageInfo.setMaxSize(bytes.length);
			packageInfo.setDynamicSize(0);

			updateLogService.addUpdateLog(packageInfo);
			pageMap.put(topic,packageInfo);
			log.info("升级开始：" + topic);
			log.info("文件大小=====》" + bytes.length);

			byte[] reOk = {ACK};
			sendDataToMqtt(topic,reOk);


		} catch (Exception e) {
			log.error("获取文件时发生异常" + e.getMessage());
			return;
		}
	}

	public void sendUpdatePackageFile(String topic,byte[] bArray){

		PackageInfo packageInfo = pageMap.get(topic);
		//没有文件、或者已经传输完成
		if(null == packageInfo || packageInfo.getDynamicSize() >= packageInfo.getMaxSize()){
			byte[] data = new byte[0];
			byte[] append = append(data, SOH);
			byte[] append1 = append(append, NUL);
			byte[] append2 = append(append1, (byte) ~NUL);
			byte[] data2 = new byte[SECTOR_SIZE];
			byte[] append5 = append(append2, data2);
			short crc3 = (short)Tools.CRC_XModem(append5);
			byte[] append6 = append(append5, shortToByteArray(crc3));
			sendDataToMqtt(topic,append6);
			return;
		}
		//第二个 C
		//SOH 01 FE  data  CRCH CRCL
		if(packageInfo.getBlockNumber() != NUL){
			packageInfo.setCNum(2);
			pageMap.put(topic,packageInfo);
			sendUpdatePackageData(topic,bArray);
			return;
		}
		//第一个 C
		//组装package 信息
		//SOH、包序号、数据、CRC校验
		//SOH 00 FF  filename  filezise NUL  CRCH CRCL
		if(packageInfo.getBlockNumber() == NUL){

			byte[] data = new byte[0];
			byte[] append = append(data, SOH);
			byte[] append1 = append(append, packageInfo.getBlockNumber());
			byte[] append2 = append(append1, (byte) ~packageInfo.getBlockNumber());

			byte[] data2 = new byte[SECTOR_SIZE];
			byte[] bytesName = append(packageInfo.getFileName().getBytes(StandardCharsets.UTF_8), NUL);
			byte[] bytesNameSize = append(("" + packageInfo.getMaxSize()).getBytes(StandardCharsets.UTF_8), NUL);

			log.info("第一包文件大小=====》" + bytesName.length);
//			byte[] append3 = append(data2, append(packageInfo.getFileName().getBytes(StandardCharsets.UTF_8),NUL));
//			byte[] append4 = append(append3, append(intToByteArray(packageInfo.getMaxSize()), NUL));

			System.arraycopy(bytesName, 0, data2, 0, bytesName.length);
			System.arraycopy(bytesNameSize, 0, data2, bytesName.length, bytesNameSize.length);
			byte[] append5 = append(append2, data2);

			short crc3 = (short)Tools.CRC_XModem(append5);
			byte[] append6 = append(append5, shortToByteArray(crc3));

			sendDataToMqtt(topic,append6);
			byte blockNumber = packageInfo.getBlockNumber();
			packageInfo.setBlockNumber((byte) ((++blockNumber) % packageInfo.getPackageSize()));
			packageInfo.setCNum(1);
			pageMap.put(topic,packageInfo);
		}
	}

	/**
	 *
	 * @param topic
	 * @param bArray
	 * 组装package 信息
	 * SOH 01 FE  data  CRCH CRCL
	 */
	public void sendUpdatePackageData(String topic,byte[] bArray){
		PackageInfo packageInfo = pageMap.get(topic);
		if(null == packageInfo){
			return;
		}

		if(packageInfo.getCNum() == 1){
			return;
		}

		//已经发送完毕的数据再次请求返回EOT
		if(packageInfo.getMaxSize() <= packageInfo.getDynamicSize()){
			byte[] eot = {EOT};
			sendDataToMqtt(topic,eot);
			return;
		}
		//正常打包、最后的数据需要补充1A
		if(packageInfo.getMaxSize() > packageInfo.getDynamicSize() + packageInfo.getPackageSize()){
			packageData(topic,packageInfo,STX);
		}else {
			packageData(topic,packageInfo,SOH);
			//
			packageInfo.setCNum(2);
			pageMap.put(topic,packageInfo);
		}
	}

	public void packageData(String topic,PackageInfo packageInfo,byte ctl){
		byte[] data = new byte[0];
		byte[] append = append(data, STX);
		byte[] append1 = append(append, packageInfo.getBlockNumber());
		byte[] append2 = append(append1, (byte) ~packageInfo.getBlockNumber());

		int getSize = packageInfo.getPackageSize();
		byte[] data2 = new byte[packageInfo.getPackageSize()];
		byte[] append3 = fileByteMap.get(packageInfo.getFileName());


		//最后一包防止数组下标越界
		if(ctl == SOH){
			getSize = packageInfo.maxSize -packageInfo.getDynamicSize();
		}

		System.arraycopy(append3, packageInfo.getDynamicSize(), data2, 0, getSize);
		byte[] append5 = append(append2, data2);

		short crc3 = (short)Tools.CRC_XModem(append5);
		byte[] append6 = append(append5, shortToByteArray(crc3));

		sendDataToMqtt(topic,append6);

		log.info("第 ： " + packageInfo.getDynamicSize()/getSize + "/" + packageInfo.getMaxSize()/getSize);
//		log.info(packageInfo.getTopic() + "数据包大小：" + packageInfo.getDynamicSize() + "/" + packageInfo.getMaxSize());

		byte blockNumber = packageInfo.getBlockNumber();
		packageInfo.setBlockNumber((byte) ((++blockNumber) % 256));
		packageInfo.setDynamicSize(packageInfo.getDynamicSize() + getSize);

		pageMap.put(topic,packageInfo);
	}

	public void clearFileMap(String topic){
		byte[] bytes = fileByteMap.get(topic);
//		fileByteMap.remove(packageInfo.getFileName());
		fileByteMap.remove(topic);
		byte[] eot = {EOT};
		sendDataToMqtt(topic,eot);
	}

	public void upDateSuccess(String topic){
		PackageInfo packageInfo = pageMap.get(topic);
		if(null != packageInfo) {
			packageInfo.getLogInfo().setStatus(2);
			updateLogService.update(packageInfo.getLogInfo());
			log.info("升级成功：" + packageInfo.getTopic());
			pageMap.remove(topic);
		}
	}


	public void upDateTOErro(String topic){
		PackageInfo packageInfo = pageMap.get(topic);
		if(null != packageInfo){
			packageInfo.getLogInfo().setStatus(3);
			updateLogService.update(packageInfo.getLogInfo());
			log.info("升级失败：" + packageInfo.getTopic());
			pageMap.remove(topic);
		}
	}
	public void clearPageMap(String topic){
		PackageInfo packageInfo = pageMap.get(topic);
		if(null != packageInfo) {
			packageInfo.getLogInfo().setNum("100");
			updateLogService.update(packageInfo.getLogInfo());
		}
	}

	public static byte[] toByteArray(InputStream input) throws IOException {
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		byte[] buffer = new byte[4096];
		int n = 0;
		while (-1 != (n = input.read(buffer))) {
			output.write(buffer, 0, n);
		}
		return output.toByteArray();
	}



	/**
	 * 将byte 加入数组中
	 * @param org
	 * @param to
	 * @return
	 */
	public static byte[] append(byte[] org, byte to) {
		byte[] newByte = new byte[org.length + 1];
		System.arraycopy(org, 0, newByte, 0, org.length);
		newByte[org.length] = to;
		return newByte;
	}

	/**
	 * 从指定数组的copy一个子数组并返回
	 *
	 * @param org of type byte[] 原数组
	 * @param to 合并一个byte[]
	 * @return 合并的数据
	 */
	public static byte[] append(byte[] org, byte[] to) {
		byte[] newByte = new byte[org.length + to.length];
		System.arraycopy(org, 0, newByte, 0, org.length);
		System.arraycopy(to, 0, newByte, org.length, to.length);
		return newByte;
	}

	/**
	 * int到byte[] 由高位到低位
	 * @param i 需要转换为byte数组的整行值。
	 * @return byte数组
	 */
	public static byte[] intToByteArray(int i) {
		byte[] result = new byte[4];
		result[0] = (byte)((i >> 24) & 0xFF);
		result[1] = (byte)((i >> 16) & 0xFF);
		result[2] = (byte)((i >> 8) & 0xFF);
		result[3] = (byte)(i & 0xFF);
		return result;
	}

	/**
	 * int到byte[] 由高位到低位
	 * @param i 需要转换为byte数组的整行值。
	 * @return byte数组
	 */
	public static byte[] shortToByteArray(short i) {
		byte[] result = new byte[2];
		result[0] = (byte)((i >> 8) & 0xFF);
		result[1] = (byte)(i & 0xFF);
		return result;
	}

	/**
	 * byte[]转int
	 * @param bytes 需要转换成int的数组
	 * @return int值
	 */
	public static int byteArrayToInt(byte[] bytes) {
		int value=0;
		for(int i = 0; i < 4; i++) {
			int shift= (3-i) * 8;
			value +=(bytes[i] & 0xFF) << shift;
		}
		return value;
	}

	/**
	 * 发送数据
	 *
	 * @param data
	 *            数据
	 * @throws
	 *
	 */
	private void sendDataToMqtt(String topic,int data){
		MqttMessage message = new MqttMessage(intToByteArray(data));
		message.setQos(2);

			log.info("发送给:"+ topic +"消息："+ bytesToHex(message.getPayload()));
//			client.publish(topic, message);
			mqttService.publish(topic,intToByteArray(data));

	}

	private void sendDataToMqtt(String topic,byte[] data){
		MqttMessage message = new MqttMessage(data);
//		message.setQos(0);

//			log.info("发送给:"+ topic +"消息："+ bytesToHex(message.getPayload()));
//			client.publish(topic, message);
		mqttService.publish(topic,data);

	}

	private void sendDataToMqtt(String topic,String data){
		MqttMessage message = new MqttMessage(data.getBytes(StandardCharsets.UTF_8));
		message.setQos(2);

			log.info("发送给:"+ topic +"消息："+ bytesToHex(message.getPayload()));
//			client.publish(topic, message);
		mqttService.publish(topic,data.getBytes(StandardCharsets.UTF_8));

		System.out.println(bytesToHex(message.getPayload()));
	}

}
